﻿using myAC.Configuration;
using myAC.PreProcessing.WordLevel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using myAC.DataProviders;



namespace myAC.FeatureVector
{
    /// <summary>
    /// DTV: Doc-Term Vector
    /// </summary>
    public class DTVHelper
    {
        Char[] separators = new[] { ' ' };  // all separators SHOULD have been replaced with <space> by now.

        public Dictionary<String, Int32> term_id = new Dictionary<string, int>();
        public Dictionary<Int32, String> id_term = new Dictionary<int, string>();

        public Dictionary<Int32,Int32>[] GenerateDTVs(ACInstance[] insts, String[] textFeatureFilter, Config cfg)
        {
            //DTV are based on selected textFeatures ONLY.
            String[] selectedTextFeatureKeys = insts[0].TextFeatures.Keys.Where(key => textFeatureFilter.Contains(key)).ToArray();
            
            Int32 instanceAmount = insts.Length;

            // We must use array here instead of List<T> for multi-threading. Because List<T> is not thread-safe and array can save us from synchronization.
            Dictionary<Int32, Int32>[] DTVs = new Dictionary<int, int>[instanceAmount]; 
            
            Parallel.For(0,instanceAmount,i=>
            {
                Dictionary<Int32, Int32> DTV = new Dictionary<int, int>();
                Dictionary<String, String> textFeatures = insts[i].TextFeatures;

                foreach (String key in selectedTextFeatureKeys)
                {
                    String docAfterLinePreprocessing = textFeatures[key]; // By now, the text SHOULD have gone through the doc-level pre-processing.
                    String[] words = docAfterLinePreprocessing.Split(separators, StringSplitOptions.RemoveEmptyEntries);  // all separators SHOULD have been replaced with <space> by now.
                    //non N-gram
                    foreach (String word in words)
                    {
                        WordLevelPipeline pipeline = new WordLevelPipeline(); //WROD LEVEL pre-processing
                        String processedWord = pipeline.Process(word);
                        Int32 id = GetId(processedWord);
                        if (DTV.ContainsKey(id)) DTV[id]++;
                        else DTV[id] = 1;
                    }

                    //N-gram
                    //TBD

                    DTVs[i] = DTV; // the same instance has identical index in DTV[] and ACInstance[].
                }
            });

            return DTVs; // The DTVs contains word count, we may need to further process it as BIT or TFIDF.
        }

        public Dictionary<Int32, Int32>[] ProcessDTVs(Dictionary<Int32, Int32>[] DTVs, Config cfg)
        {
            switch (cfg.WeightMethod)
            {
                case WeightMethod.BIT:
                    return ProcessDTVsBIT(DTVs);
                case WeightMethod.COUNT: // nothing need to do.
                case WeightMethod.TFIDF: // not implemented yet.
                default:
                    return DTVs;
            }
        }

        private Dictionary<Int32, Int32>[] ProcessDTVsBIT(Dictionary<Int32, Int32>[] DTVs)
        {
            Int32 length = DTVs.Length;
            Dictionary<Int32, Int32>[] bitDTVs = new Dictionary<int, int>[length];
            Parallel.For(0, length, i =>
            {
                Dictionary<Int32, Int32> bitDTV = new Dictionary<int, int>();
                foreach (Int32 key in DTVs[i].Keys)
                {
                    bitDTV[key] = 1;
                }
                bitDTVs[i] = bitDTV;
            });

            return bitDTVs;
        }


        object asyncLock = new object();

        private Int32 GetId(String word)
        {
            lock(asyncLock)
            {
                if (!term_id.ContainsKey(word))
                {
                    Int32 count = term_id.Count();
                    id_term[count] = word;
                    term_id[word] = count;
                    return count;
                }
                return term_id[word];
            }
        }

    }
}
